﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;

namespace Xceed.Silverlight.Data
{
  internal sealed class DataSourceGroupContext
  {
    #region Constructors

    internal DataSourceGroupContext( GroupHandler handler )
      : this(
        handler.GroupDescriptions,
        handler.GroupCollections,
        handler.GroupHeadersFootersSelector,
        handler.GroupStatFunctionsSelector,
        handler.GroupCountPolicy,
        handler.IncludeGroupItems,
        handler.IncludeGroupItemsCount )
    {
    }

    private DataSourceGroupContext()
    {
      m_readOnlyGroupDescriptions = new ReadOnlyCollection<GroupDescription>( m_groupDescriptions );
    }

    private DataSourceGroupContext( DataSourceGroupContext copy )
      : this(
        copy.GroupDescriptions,
        copy.GroupCollections,
        copy.GroupHeadersFootersSelector,
        copy.GroupStatFunctionsSelector,
        copy.GroupCountPolicy,
        copy.IncludeGroupItems,
        copy.IncludeGroupItemsCount )
    {
    }

    private DataSourceGroupContext(
      IEnumerable<GroupDescription> groupDescriptions,
      IEnumerable<IGroupCollection> groupCollections,
      IGroupHeadersFootersSelector groupHeadersFootersSelector,
      IGroupStatFunctionsSelector groupStatFunctionsSelector,
      GroupCountPolicy groupCountPolicy,
      bool includeGroupItems,
      bool includeGroupItemsCount )
      : this()
    {
      if( groupDescriptions.Count() != groupCollections.Count() )
        throw new ArgumentException( "groupCollections" );

      m_groupDescriptions.AddRange( groupDescriptions );
      m_groupCollections = new GroupLevelCollection( DataSourceGroupContext.Copy( groupCollections ) );
      m_groupHeadersFootersSelector = groupHeadersFootersSelector;
      m_groupStatFunctionsSelector = groupStatFunctionsSelector;
      m_groupCountPolicy = groupCountPolicy;
      m_includeGroupItems = includeGroupItems;
      m_includeGroupItemsCount = includeGroupItemsCount;
    }

    #endregion

    #region GroupDescriptions Property

    internal IList<GroupDescription> GroupDescriptions
    {
      get
      {
        return m_readOnlyGroupDescriptions;
      }
    }

    private readonly List<GroupDescription> m_groupDescriptions = new List<GroupDescription>();
    private readonly ReadOnlyCollection<GroupDescription> m_readOnlyGroupDescriptions;

    #endregion

    #region GroupCollections Property

    internal GroupLevelCollection GroupCollections
    {
      get
      {
        return m_groupCollections;
      }
    }

    private readonly GroupLevelCollection m_groupCollections;

    #endregion

    #region GroupNames Property

    internal IEnumerable<string> GroupNames
    {
      get
      {
        return DataUtils.GetGroupNames( this.GroupDescriptions );
      }
    }

    #endregion

    #region GroupHeadersFootersSelector Property

    internal IGroupHeadersFootersSelector GroupHeadersFootersSelector
    {
      get
      {
        return m_groupHeadersFootersSelector;
      }
    }

    private readonly IGroupHeadersFootersSelector m_groupHeadersFootersSelector;

    #endregion

    #region GroupStatFunctionsSelector Property

    internal IGroupStatFunctionsSelector GroupStatFunctionsSelector
    {
      get
      {
        return m_groupStatFunctionsSelector;
      }
    }

    private readonly IGroupStatFunctionsSelector m_groupStatFunctionsSelector;

    #endregion

    #region GroupCountPolicy Property

    internal GroupCountPolicy GroupCountPolicy
    {
      get
      {
        return m_groupCountPolicy;
      }
    }

    private readonly GroupCountPolicy m_groupCountPolicy;

    #endregion

    #region IncludeGroupItems Property

    internal bool IncludeGroupItems
    {
      get
      {
        return m_includeGroupItems;
      }
    }

    private readonly bool m_includeGroupItems;

    #endregion

    #region IncludeGroupItemsCount Property

    internal bool IncludeGroupItemsCount
    {
      get
      {
        return m_includeGroupItemsCount;
      }
    }

    private readonly bool m_includeGroupItemsCount;

    #endregion

    #region AnyHeaderFooter Property

    internal bool AnyHeaderFooter
    {
      get
      {
        IGroupHeadersFootersSelector selector = this.GroupHeadersFootersSelector;

        if( selector != null )
        {
          ICollection<GroupDescription> groupDescriptions = this.GroupDescriptions;

          for( int i = 0; i < groupDescriptions.Count; i++ )
          {
            DataPath dataPath = new DataPath( Enumerable.Repeat( GroupDataItem.Any, i + 1 ).ToArray() );

            if( ( selector.HasHeaders( dataPath ) ) || ( selector.HasFooters( dataPath ) ) )
              return true;
          }
        }

        return false;
      }
    }

    #endregion

    #region AnyCollapsedGroup Property

    internal bool AnyCollapsedGroup
    {
      get
      {
        foreach( IGroupCollection collection in m_groupCollections )
        {
          if( collection.AnyCollapsedGroup.GetValueOrDefault( collection.IsCollapsedByDefault ) )
            return true;
        }

        return false;
      }
    }

    #endregion

    #region AnyGroup Property

    internal bool AnyGroup
    {
      get
      {
        return ( m_groupCollections.Count > 0 );
      }
    }

    #endregion

    public override int GetHashCode()
    {
      int hashCode = 11;

      hashCode += 17 * this.GroupDescriptions.Count;
      hashCode += 17 * DataSourceGroupContext.GetHashCode( this.GroupHeadersFootersSelector );
      hashCode += 17 * DataSourceGroupContext.GetHashCode( this.GroupStatFunctionsSelector );
      hashCode += 17 * this.GroupCountPolicy.GetHashCode();
      hashCode += 17 * this.IncludeGroupItems.GetHashCode();
      hashCode += 17 * this.IncludeGroupItemsCount.GetHashCode();

      return hashCode;
    }

    public override bool Equals( object obj )
    {
      if( object.ReferenceEquals( obj, this ) )
        return true;

      DataSourceGroupContext context = obj as DataSourceGroupContext;

      if( object.ReferenceEquals( context, null ) )
        return false;

      if( context.GetHashCode() != this.GetHashCode() )
        return false;

      return ( object.Equals( context.GroupHeadersFootersSelector, this.GroupHeadersFootersSelector ) )
          && ( object.Equals( context.GroupStatFunctionsSelector, this.GroupStatFunctionsSelector ) )
          && ( object.Equals( context.GroupCountPolicy, this.GroupCountPolicy ) )
          && ( object.Equals( context.IncludeGroupItems, this.IncludeGroupItems ) )
          && ( object.Equals( context.IncludeGroupItemsCount, this.IncludeGroupItemsCount ) )
          && ( context.GroupDescriptions.SequenceEqual( this.GroupDescriptions ) )
          && ( context.GroupCollections.SequenceEqual( this.GroupCollections ) );
    }

    internal DataSourceGroupContext Clone()
    {
      return new DataSourceGroupContext( this );
    }

    internal IAsyncEnumerator<DataPath> GetHeadersEnumerator( DataPath groupPath )
    {
      IAsyncEnumerator<object> childEnumerator = this.GetHeadersFootersEnumerator( groupPath, true );

      return this.ConvertEnumerator( groupPath, HeaderFooterType.Header, childEnumerator );
    }

    internal IAsyncEnumerator<DataPath> GetFootersEnumerator( DataPath groupPath )
    {
      IAsyncEnumerator<object> childEnumerator = this.GetHeadersFootersEnumerator( groupPath, false );

      return this.ConvertEnumerator( groupPath, HeaderFooterType.Footer, childEnumerator );
    }

    private static int GetHashCode( object obj )
    {
      if( object.ReferenceEquals( obj, null ) )
        return 0;

      return obj.GetHashCode();
    }

    private static IEnumerable<IGroupCollection> Copy( IEnumerable<IGroupCollection> source )
    {
      foreach( IGroupCollection collection in source )
      {
        GroupCollection gc = collection as GroupCollection;

        if( gc != null )
        {
          yield return gc.Clone().AsReadOnly();
        }
        else
        {
          gc = new GroupCollection();

          if( collection.IsCollapsedByDefault )
          {
            gc.CollapseAll();
          }
          else
          {
            gc.ExpandAll();
          }

          ICollection<DataPath> groupCollection = gc;

          foreach( DataPath path in collection )
          {
            groupCollection.Add( path );
          }

          yield return gc.AsReadOnly();
        }
      }
    }

    private IAsyncEnumerator<object> GetHeadersFootersEnumerator(
      DataPath groupPath,
      bool isHeader )
    {
      if( groupPath == null )
        throw new ArgumentNullException( "groupPath" );

      if( !( groupPath.LastChild is GroupDataItem ) )
        throw new ArgumentException( "path must refer to a group", "groupPath" );

      IEnumerable<object> headersFooters = null;
      IGroupHeadersFootersSelector selector = this.GroupHeadersFootersSelector;

      if( selector != null )
      {
        if( isHeader )
        {
          headersFooters = selector.GetHeaders( groupPath );
        }
        else
        {
          headersFooters = selector.GetFooters( groupPath );
        }
      }

      List<object> data;

      if( headersFooters != null )
      {
        data = headersFooters.ToList();
      }
      else
      {
        data = new List<object>( 0 );
      }

      return new SyncToAsyncEnumerator<object>(
        new LocalListEnumerator<object>( data ) );
    }

    private IAsyncEnumerator<DataPath> ConvertEnumerator( DataPath groupPath, HeaderFooterType headerType, IAsyncEnumerator<object> childEnumerator )
    {
      return new HeaderFooterEnumerator( childEnumerator, groupPath, headerType );
    }

    #region HeaderFooterEnumerator Nested Type

    private class HeaderFooterEnumerator : ConversionEnumerator<object, DataPath>
    {
      internal HeaderFooterEnumerator( IAsyncEnumerator childEnumerator, DataPath groupPath, HeaderFooterType headerType )
        : base( childEnumerator )
      {
        if( groupPath == null )
          throw new ArgumentNullException( "groupPath" );

        m_groupPath = groupPath;
        m_type = headerType;
      }

      protected override DataPath Convert( DataSourceContext context, object value )
      {
        return m_groupPath.CreateChildPath( new HeaderFooterDataItem( value, m_type ) );
      }

      protected override object ConvertBack( DataSourceContext context, DataPath value )
      {
        HeaderFooterDataItem dataItem = ( HeaderFooterDataItem )value.LastChild;

        return dataItem.Data;
      }

      private readonly DataPath m_groupPath;
      private readonly HeaderFooterType m_type;
    }

    #endregion
  }
}
